﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;   // For DLL importing 
using System.IO;
using System.Text;
using System.Windows.Forms;
using NDesk.Options;

namespace Macaw.SolutionsFactory.FactoryIdeTools.CCNetBuildStation
{
    public partial class FormBuildStation : Form
    {
        // Some code needed to hide a window of a process
        [DllImport("User32")]
        private static extern int ShowWindow(int hwnd, int nCmdShow);
        const int SW_HIDE = 0;


        static OptionSet CommandLineOptions { get; set; }

        // Configurable through command-line arguments 
        static string AppTitle { get; set; }
        static string CCNetFolder { get; set; }
        static string CCNetConfigPath { get; set; }
        static string RollingLogItems { get; set; }
        static string WebServerPath { get; set; }
        static string WebServerPort { get; set; }
        static string Verbose { get; set; }
        static bool ShowHelp { get; set; }

        // Derived configuration
        private int MaxLogItems { get; set; }
        private bool VerboseMode { get; set; }
        private string WebServerArguments { get; set; }
        private string CCNetPath { get; set; }
        private string CCNetArguments { get; set; }



        Uri CCNetHomeUri;

        public Process CCNetProcess = null;
        public Process WebServerProcess = null;

        public FormBuildStation()
        {
            InitializeComponent();

            // Default values to get started directly
            MaxLogItems = 1000;
            VerboseMode = true;
        }

        private void FormCCNetBuildStation_Load(object sender, EventArgs e)
        {
            InitializeCommandLineOptions();
            ParseCommandLineOptions();
            if (ShowHelp)
            {
                ShowHelpText(CommandLineOptions);
            }
            else
            {
                bool errors = ValidateCommandLineOptions();
                if (!errors)
                {
                    // Set the application title
                    if (AppTitle != null)
                    {
                        this.Text = AppTitle;
                    }

                    VerboseLog("Starting CruiseControl.Net...");
                    StartCCNet();
                    VerboseLog("CruiseControl.Net started.");

                    VerboseLog(String.Format(@"Starting Web Server ""{0}"" for serving CruiseControl.Net webdashboard...", WebServerPath));
                    StartWebServer();
                    VerboseLog("Web Server started.");

                    VerboseLog(String.Format("Navigating to webdashboard home page: {0}", CCNetHomeUri));

                    webBrowser.Url = CCNetHomeUri;

                    // On first title change, hide the web server process window
                    webBrowser.DocumentTitleChanged += webBrowser_DocumentTitleChanged;
                }
            }
        }

        /// <summary>
        /// Hide the web server process window on first load of web page.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void webBrowser_DocumentTitleChanged(object sender, EventArgs e)
        {
            int hWnd = WebServerProcess.MainWindowHandle.ToInt32();
            ShowWindow(hWnd, SW_HIDE);

            // Only needed on first event, remove it now.
            webBrowser.DocumentTitleChanged -= webBrowser_DocumentTitleChanged;
        }

        private static void InitializeCommandLineOptions()
        {
            CommandLineOptions = new OptionSet() {
                {
                    "title=",
                    "Title to display in the application window, default CCNetBuildStation [Optional]",
                    v => AppTitle = v
                },
                { 
                    "ccnetfolder=",     
                    "The path to the folder where CruiseControl.Net is installed [Required]", 
                    v => CCNetFolder = v
                }, 
                {
                    "ccnetconfigpath=", 
                    "The path to the CruiseControl.Net configuration file (like ccnet.config) [Required]",
                    v => CCNetConfigPath = v
                }, 
                {
                    "webserverpath=",    
                    "Path to the Casini or Visual Studio WebDev.WebServer compatible web server application. If not specified the built-in Cassini web server will be used [Optional]",
                    v => WebServerPath = v
                },
                {
                    "webserverport=",
                    "Port to run the CruiseControl.Net web portal on, use port not used by IIS, for example 1234 [Required]",
                    v => WebServerPort = v
                }, 
                {
                    "rollinglogitems=",
                    "Number of lines to show in the log window, default 1000 [Optional]",
                    v => RollingLogItems = v
                }, 
                {
                    "verbose=",
                    "Verbosity mode (on, off), default 'on' [Optional]",
                    v => Verbose = v
                }, 
                {
                    "h|?|help",
                    "Show help in the log window. The CruiseControl.Net and the web server are not started.",
                    v => ShowHelp = v != null
                },
            };
        }

        private void ParseCommandLineOptions()
        {
            // Get arguments minus the executable name
            List<string> args = new List<string>(Environment.GetCommandLineArgs());
            args.RemoveAt(0);

            List<string> extra = CommandLineOptions.Parse(args);
            foreach (var extraItem in extra)
            {
                Log(String.Format("Unexpected argument '{0}' found [ignored]", extraItem));
            }
        }

        private bool ValidateCommandLineOptions()
        {
            bool error = false;

            // Check if all arguments are specified and have a correct value

            if (CCNetFolder == null)
            {
                Log(
                    "Error: The CruiseControl.Net installation folder is not specified using the 'ccnetfolder' command-line argument");
                error = true;
            }
            else
            {
                if (!Directory.Exists(CCNetFolder))
                {
                    Log(
                        String.Format(
                            "Error: The CruiseControl.Net installation folder '{0}' specified as the 'ccnetfolder' command-line argument does not exist",
                            CCNetFolder));
                    error = true;
                }
            }

            if (CCNetConfigPath == null)
            {
                Log(
                    "Error: The CruiseControl.Net configuration file is not specified using the 'ccnetconfigpath' command-line argument");
                error = true;
            }
            else
            {
                if (!File.Exists(CCNetConfigPath))
                {
                    Log(
                        String.Format(
                            "Error: The CruiseControl.Net configuration file '{0}' specified as the 'ccnetconfigpath' command-line argument does not exist",
                            CCNetConfigPath));
                    error = true;
                }
            }

            // Optional
            if (WebServerPath != null)
            {
                if (!File.Exists(WebServerPath))
                {
                    Log(
                        String.Format(
                            "Error: The web server application file '{0}' specified as the 'webserverpath' command-line argument does not exist",
                            WebServerPath));
                    error = true;
                }
            }
            else
            {
                WebServerPath = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location).ToString(),
                                             "Cassini-v35.exe");
                if (!File.Exists(WebServerPath))
                {
                    Log(
                        String.Format(
                            "Error: The default Cassini web server application file '{0}' that is the default value in the case of a missing 'webserverpath' command-line argument does not exist",
                            WebServerPath));
                    error = true;
                }
            }

            if (WebServerPort == null)
            {
                Log("Error: The web server port is not specified using the 'webserverport' command-line argument");
                error = true;
            }
            else
            {
                ushort port;
                if (!ushort.TryParse(WebServerPort, out port) || port == 0)
                {
                    Log(
                        String.Format(
                            "Error: The web server port '{0}' specified as the 'webserverport' command-line argument is invalid. Use a value 1..65535.",
                            WebServerPort));
                    error = true;
                }
            }

            if (RollingLogItems == null)
            {
                RollingLogItems = "1000";
                Log(
                    "The number of rolling log items is not specified as a 'rollinglogitems' command-line argument. Default value of 10000 is used");
            }
            else
            {
                ushort items;
                if (!ushort.TryParse(RollingLogItems, out items) || items == 0)
                {
                    Log(
                        String.Format(
                            "Error: The number of rolling log items '{0}' specified as the 'rollinglogitems' command-line argument is invalid. Use a value 1..65535.",
                            RollingLogItems));
                    RollingLogItems = "1000";
                    error = true;
                }
            }

            if (Verbose == null)
            {
                Verbose = "on";
                Log(
                    "The verbosity level is not specified as a 'verbose' command-line argument. Default value of 'on' is used");
            }
            else
            {
                if (Verbose != "on" && Verbose != "off")
                {
                    Log(
                        String.Format(
                            "Error: The verbosity level '{0}' specified as the 'verbose' command-line argument is invalid. Use a value 'on' or 'off'",
                            Verbose));
                    error = true;
                }
            }

            if (!error)
            {
                // Derived configurations
                MaxLogItems = ushort.Parse(RollingLogItems);
                VerboseMode = Verbose == "on";
                string webDashboardFolder = Path.Combine(CCNetFolder, "webdashboard");
                if (!Directory.Exists(webDashboardFolder))
                {
                    Log(String.Format("Error: The expected CruiseControl.Net webdashboard folder '{0}' does not exist",
                                      webDashboardFolder));
                    error = true;
                }
                if (WebServerPath.Contains("Cassini"))
                {
                    WebServerArguments = String.Format(@"""{0}"" {1} ""/ccnet""", webDashboardFolder, WebServerPort);
                }
                else if (WebServerPath.Contains("WebDev.WebServer"))
                {
                    WebServerArguments = String.Format(@"/port:{0} /path:""{1}"" /vpath:""/ccnet""", WebServerPort,
                                                       webDashboardFolder);
                }
                else
                {
                    Log(
                        "Only the Cassini and Visual Studio WebDev.WebServer local web servers are supported. The Cassini web server is provided with this distribution.");
                    error = true;
                }
                CCNetHomeUri = new Uri(String.Format("http://localhost:{0}/ccnet", WebServerPort));
                CCNetPath = Path.Combine(CCNetFolder, @"server\ccnet.exe");
                if (!File.Exists(CCNetPath))
                {
                    Log(String.Format("Error: the CruiseControl.Net executable '{0}' does not exist", CCNetPath));
                    error = true;
                }

                CCNetArguments = String.Format(@"-config:""{0}"" -remoting:on", CCNetConfigPath);
            }

            return error;
        }

        delegate void UpdateLogDelegate(String msg);

        void StartCCNet()
        {
            string filename = CCNetPath;
            string arguments = CCNetArguments;

            CCNetProcess = new Process();
            CCNetProcess.StartInfo.CreateNoWindow = true;
            CCNetProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            CCNetProcess.StartInfo.UseShellExecute = false;
            CCNetProcess.StartInfo.FileName = filename;
            // Start in the directory where the executable resides, otherwise NetReflector can't find plugins like the vsts sourcecontrol provider
            CCNetProcess.StartInfo.WorkingDirectory = Path.GetDirectoryName(filename);
            CCNetProcess.StartInfo.Arguments = arguments;
            CCNetProcess.StartInfo.RedirectStandardOutput = true;
            CCNetProcess.StartInfo.RedirectStandardError = true;
            // Set event handler to asynchronously read the output
            CCNetProcess.OutputDataReceived += 
                    (sendingProcess, received) => { if (!String.IsNullOrEmpty(received.Data)) Log(received.Data); }
                                             ;

            CCNetProcess.Start();
            VerboseLog(String.Format("CruiseControl.NET Process ID: {0}", CCNetProcess.Id));

            // Start processing output lines
            CCNetProcess.BeginOutputReadLine();
        }

        public void Log(string message)
        {
            // Check whether the caller must call an invoke method when making method calls to listBoxLog because the caller is 
            // on a different thread than the one the listBoxLog control was created on.
            if (listBoxLog.InvokeRequired)
            {
                UpdateLogDelegate update = new UpdateLogDelegate(Log);
                listBoxLog.Invoke(update, message);
            }
            else
            {
                listBoxLog.Items.Add(message);
                if (listBoxLog.Items.Count > MaxLogItems)
                {
                    listBoxLog.Items.RemoveAt(0); // remove first line
                }
                // Make sure the last item is made visible
                listBoxLog.SelectedIndex = listBoxLog.Items.Count - 1;
                listBoxLog.ClearSelected();
            }
        }

        public void VerboseLog(string message)
        {
            if (VerboseMode)
            {
                Log(message);
            }
        }

        private void CCNetOutputHandler(object sendingProcess, DataReceivedEventArgs received)
        {
            if (!String.IsNullOrEmpty(received.Data))
            {
                Log(received.Data);
            }
        }

        void StartWebServer()
        {
            string filename = WebServerPath;
            string arguments = WebServerArguments;

            WebServerProcess = new Process();
            WebServerProcess.StartInfo.CreateNoWindow = true;
            WebServerProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            WebServerProcess.StartInfo.UseShellExecute = false;
            WebServerProcess.StartInfo.FileName = filename;
            WebServerProcess.StartInfo.Arguments = arguments;
            WebServerProcess.Start();
            VerboseLog(String.Format("Web Server Process ID: {0}", WebServerProcess.Id));
        }


        private void clearLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            listBoxLog.Items.Clear();
        }


        public void ShowHelpText(OptionSet p)
        {
            Log("--------------------------------------");
            Log(String.Format("Usage: {0} [Options]", Environment.GetCommandLineArgs()[0]));
            Log("Options:");

            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            p.WriteOptionDescriptions (sw);
            string help = sb.ToString();
            string[] helpLines = help.Split(new string[] {Environment.NewLine}, StringSplitOptions.None);
            foreach (string line in helpLines)
            {
                Log(line);
            }
            Log("--------------------------------------");
        }

        private void ShowConfigurationText()
        {
            Log("--------------------------------------");
            string[] args = Environment.GetCommandLineArgs();
            Log(String.Format("Application \"{0}\" is invocated with the following command-line arguments:", args[0]));
            for (int i=1; i<args.Length; i++)
            {
                Log(args[i]);
            }
            Log("--------------------------------------");
        }

        private void aboutCCNetBuildStationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FormAbout about = new FormAbout();
            about.ShowDialog();
        }

        private void homeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            webBrowser.Url = CCNetHomeUri;
        }

        private void previousPageToolStripMenuItem_Click(object sender, EventArgs e)
        {
            webBrowser.GoBack();
        }

        /// <summary>
        /// Exit the application.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void showApplicationCommandlineArgumentsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowHelpText(CommandLineOptions);
        }

        private void showcurrentConfigurationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowConfigurationText();
        }

        private void showCruiseControlNetDocumentationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Uri uri = new Uri(Path.Combine(CCNetFolder, @"webdashboard\doc\index.html"));
            VerboseLog(String.Format("CruiseControl.Net Documenttion URI: {0}", uri.ToString()));
            webBrowser.Url = uri;

        }

        private void showCCNetBuildStationHomepageOnCodeplexToolStripMenuItem_Click(object sender, EventArgs e)
        {

            Uri uri = new Uri("http://www.codeplex.com/CCNetBuildStation");
            VerboseLog(String.Format("CCNetBuildStation on codeplex URI: {0}", uri.ToString()));
            webBrowser.Url = uri;
        }

        private void copyLogToClipboardToolStripMenuItem_Click(object sender, EventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            int lines = listBoxLog.Items.Count;
            for (int i=0; i<lines; i++)
            {
                Object item = listBoxLog.Items[i];
                if (item != null)
                {
                    sb.AppendLine(item.ToString());
                }
                else
                {
                    break;
                }
            }

            Clipboard.SetText(sb.ToString());
        }
    }
}
